Release 10.1A: OpenEdge Development:
Progress 4GL Handbook
Adding dynamic fields to the test window
The first thing you need to add to
h-CustOrderWin7.wto create dynamic fields is a variable or other storage to hold their handles. Because the whole purpose of the exercise is to allow the user to select a variable number of fields to display, there is no reasonable way to store each one’s handle in a separate variable.Storing a list of handles
You could store the object handles in a
HANDLEvariable array that has anEXTENT, but this is almost certainly a bad idea. The first rule of using a variable with an extent is that you should do it only when the proper value for the extent is clear, based on the nature of the data it is holding, such as values for the seven days in a week or the twelve months in a year. If you just try to pick a value that seems big enough, you will often regret it later when that number turns out to be too small for some case you hadn’t anticipated.The method used in the example is just to store the handles in a list, in character form. For a modest number of values, this is quite reasonable, and the conversion effort back and forth between a handle and its character representation is not significant.
Always keep in mind the alternative of using a temp-table to store a set of values during program execution. Although the overhead of having to perform a
FINDon what amounts to a special database table may seem significant, in fact temp-tables are extremely fast. Most or all of the records you need to work with will likely be in memory anyway and, with the ability to index fields that you need to retrieve or filter on, even a large temp-table should provide very good performance. A temp-table is well suited to situations where the number of possible values you need to keep track of can grow large. How large is large? There’s no precise answer to this, but it is probably a good rule of thumb that if you’re storing more than a few dozen values, it is cleaner and possibly faster to use a temp-table. A temp-table is also the right choice when you need to store several related pieces of information for each item, each of which can become a field in the temp-table definition.For this example you simply use a character variable. Its value needs to persist for the life of the procedure, because the handles are saved off by one internal procedure or trigger block and used by another.
![]()
To create dynamic fields in the sample window:
- Define the
cFieldHandlesvariable in the Definitions section ofh-CustOrderWin7.w, which scopes the variable definition to the whole procedure:
- Write a block of code to execute whenever a new Order is selected. This is the
VALUE-CHANGEDevent for the browse, which you’ve used in an earlier variation of this procedure.The code in the
VALUE-CHANGEDtrigger needs to find the first OrderLine for the Order. For the sake of simplicity, the example does not navigate through all the OrderLines, but you could easily extend it to do this. Then, it looks at the existing list of dynamic field handles (if any) and clears them out by setting theirSCREEN-VALUEto blank:
- Define a
LEAVEtrigger for the OLineFields selection list. The trigger uses these variables:
- To allow for the case where this is not the first time the user has selected a list of fields, add code that first deletes the existing fields using their object handles, which are stored in a list in the
cFieldsHandlevariable:
Remember that if you neglect to do this, each new request would add more objects to the session that aren’t being used anymore. The
NO-ERRORqualifier on theDELETE OBJECTstatement simply suppresses any error message in the event that the object has already been deleted in some other way.How about when the procedure is terminated? Do you need code to delete the dynamic fields that are around at that time to prevent a memory leak? The answer is no, but only because of the widget pool created in the Definitions section, which cleans up all dynamic objects created by the procedure when the procedure terminates. That’s why the widget pool convention is so valuable. Without the widget pool created for the procedure, you could leave dynamic objects in memory for the duration of the session, even after the procedure exits.
Since this code is the
LEAVEtrigger for the selection list, the field’sSCREEN-VALUEattribute holds the value the user selected. In the case of a multiple-selection list such as this, the value is actually a comma-separated list of all the entries the user selected.- Save this value in a variable to keep the rest of the code from having to refer to the
SCREEN-VALUEattribute over and over again:
- Add a block to iterate through all the selections. You saw earlier how the
BUFFER-FIELDattribute on a buffer handle can take the ordinal position of the field in the buffer as an identifier. You can also pass the field name, as the code does here. Once you’ve retrieved the handle of the selected field, the code can query a number of different field attributes through that handle:
- Create the text label for the fill-in. As you learned earlier, the label must be a separate text object:
The
CREATEstatement parents it to the frame, sets its data type, calculates a format and value for it using theLABELattribute of the current buffer field, and positions it in the frame. TheHEIGHT-CHARSof 1 makes the label text align properly with the value displayed next to it. TheCOLUMNpositions it next to the browse, and the row is incremented each time through the loop to define a distinct position for each field.- Create the fill-in object itself:
The data type, format, and value all come from the buffer field object handle. The
SIDE-LABEL-HANDLEattribute connects this fill-in to its handle object. TheCOLUMNsetting allows room for the label before displaying the field value. TheSCREEN-VALUEassigns the value from the buffer field’sBUFFER-VALUEattribute. TheHIDDENattribute makes sure the field is viewed along with the frame that contains it.- Increment the row counter to set the position of the next field, and save off the handles of the labels and fill-ins in a list:
- Make sure that the
VALUE-CHANGEDtrigger for the Order browse fires whenever a different record is displayed. This includes when the procedure first starts up, so make this addition to the main block:
- Make the same addition to each of the navigation button triggers, as you have done to another version of the procedure in Chapter 7, " Record Buffers and Record Scope."
- Run the window. Now you can select one or more fields from the selection list, tab out of it, and see those fields displayed as dynamic fill-ins with dynamic labels next to the browse:
![]()
If a few of the fields seem to be positioned rather far to the right (the Order Num for instance), it’s because they are right-justified numeric fields with overly generous display formats as defined in the Data Dictionary. Specifically, the OrderNum and ItemNum fields are defined in the schema with a long format that uses the Z character to format leading zeros. The Z tells Progress to replace leading zeroes with spaces, which pushes the displayed value out to the right. Others, such as the Price, are formatted with the > character, which tells Progress to suppress leading zeroes, effectively left-justifying the value. This is just a result of the formatting choices made by the database designer and has nothing to do with the display of dynamic values.
Using the FONT-TABLE to make the labels colon-aligned
This display looks all right as far as it goes, but in many cases you want your labels to appear right-justified rather than left-justified. In other words, you want the colons that end each label to be vertically aligned, so that all the field values can begin at the same column position to the right of that. How can you do this?
Progress provides a built-in system handle, called
FONT-TABLE, which is an object representing the current font. There are four useful methods you can apply to this handle to calculate the actual size of a value when it’s displayed:GET-TEXT-WIDTH-CHARS,GET-TEXT-HEIGHT-CHARS,GET-TEXT-WIDTH-PIXELS, andGET-TEXT-HEIGHT-PIXELS. In an alternative version of the trigger code for the selection list, you can use this function to align the labels on their colons.
![]()
To colon-align your labels:
- Change the column setting in the
CREATE TEXTstatement for the label to this:
Instead of starting at column 85 and positioning the label to the right, the statement starts where the labels should end (column position 100), and subtracts the label width as calculated by the method on the
FONT-TABLE. This gives the right starting position for the label object.- Change the column assignment for the fill-in to be fixed at column 102:
This places each displayed value at the same position, two positions to the right of the label.
There’s a third change you have to make as well. As discussed in the "Object sizing" section, the format calculation for the label is likely to provide a format somewhat larger than the actual display width. This is a deliberate adjustment Progress makes to provide a format for a field that is large enough to display most values without truncation. In the case of your labels, however, you might find that if you just use the label format to determine the width, the display width is a bit too large and overwrites the beginning of some of the displayed values with blanks. To correct this, you need to specify an accurate
WIDTH-CHARSattribute value for the label, so that it is just large enough to display itself without truncation but not so large that it overwrites the value that follows it.- Use the same
FONT-TABLEmethod to calculate theWIDTH-CHARSof the text label object, adding this assignment to theCREATE TEXTstatement:
- Run the window. With these changes, you see a different display where the labels of the dynamic fields are colon-aligned:
![]()
- Select another row in the browse, and the field values are set to blank by the
VALUE-CHANGEDtrigger on the browse. What you see, however, is that because the displayed fields are not allCHARACTERfields, Progress applies the field’s format to the blank value, which in the case of a numeric field is the value zero, and is displayed in various ways by the different field formats:
![]()
Clearly this procedure is an interesting example of using dynamic fields but not a terribly useful application window. You would probably want to be able to select a list of fields once and use them to display OrderLine values for any Order you select, rather than having the fields blanked out after one OrderLine is displayed. And there must be a way to navigate through the OrderLines of an Order rather than just seeing the first one. Feel free to extend the test procedure to provide these abilities.
Since there’s room in the window to display all the OrderLine fields, the whole notion of making them dynamic fill-ins is also of limited use. A more realistic example of how you can use this technique in an application would be for a table with a great many fields, only a few of which each user needs to work with. After you learn about dynamic data management objects in the next chapter, you will also be able to allow the user to select a completely different table to display in place of the OrderLines.
Field format versus width
The relationship between format and width that the example highlights might seem confusing. Keep these basic guidelines in mind:
- The
FORMATis the maximum number of characters the user is allowed to type into the field at run time.- The
WIDTH-CHARS(orWIDTH-PIXELSif you measure your objects that way) is the actual screen real estate allotted to the object, whether it’s for display or for data entry purposes. You can use theFONT-TABLEmethods to calculate a precise width for a single displayed value, but you must estimate an appropriate width for a field that can be used to display many values, for example as you navigate through many records in a table.- If you assign a format but not a width, Progress estimates a width based on the format. This normally is slightly greater that the width as based on
WIDTH-CHARS, because Progress tries to provide additional space to prevent truncation of, in particular, short values with wide characters (such as a two-character capitalized state abbreviation, for instance). Therefore, you must assign an explicit width (usingWIDTH-CHARSorWIDTH-PIXELS) if you want to make sure the display space isn’t bigger than it needs to be, for example to prevent overwriting an object displayed next to the object you’re sizing.- If, on the other hand, you assign a width but not a format, Progress uses a default display format for the data type. For a
CHARACTERfield, for example, this is“X(8)”. This is not likely to be appropriate, so you almost always need to assign a format.- If the format is larger than the display width for a
CHARACTERfill-in field that is enabled for input, the user can type as many characters as the format allows. If the display space runs out, the fill-in automatically scrolls to allow the user to enter more data. This is very often a desirable user interface design, which displays a reasonable number of characters for a field but allows the user to type more when necessary (and have it all stored in the underlying field).- If the format for a
CHARACTERfill-in is smaller than the display width and the user tries to type more characters than the format allows, Progress prevents this. The user gets only a warning bell as he continues to type, even though there is still visible display space available that was allocated by the width attribute. This is a very bad user interface design, which leads to the following basic guiding principle.- When in doubt, make the format generously large and the width appropriate for the display. Limit the format size for enabled fields only when it is important to limit the number of characters stored in the underlying variable or database field. Remember that all Progress
CHARACTERvariables and fields are inherently variable width, so the format of a database field does not allocate a fixed storage size, but only what a particular field value uses for each record.
|
Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095 |